/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2019.
 * Description: support easy dfx feature
 * Author: qiuguorui
 * Create: 2019-05-21
 */
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/kdebug.h>
#include <linux/reboot.h>
#include <linux/hal/easy_dfx_hook.h>
#include <trace/events/ipi.h>

/* use kernel die_chain */
static int register_dfx_kernel_die_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR"Register easy dfx kernel die notifier failed\n");
		return -EINVAL;
	}

	return register_die_notifier(nb);
}

static int unregister_dfx_kernel_die_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR"Unregister easy dfx kernel die  notifier failed\n");
		return -EINVAL;
	}

	return unregister_die_notifier(nb);
}

static ATOMIC_NOTIFIER_HEAD(dfx_usr_unhandled_fault_notifier_chain);
static int register_dfx_usr_unhandled_fault_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "register unhandled_fault notifier failed\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_register(&dfx_usr_unhandled_fault_notifier_chain, nb);
}

static int unregister_dfx_usr_unhandled_fault_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "unregister usr_die notifier failed\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_unregister(&dfx_usr_unhandled_fault_notifier_chain, nb);
}

int call_dfx_usr_unhandled_fault_notifiers(void)
{
	return atomic_notifier_call_chain(&dfx_usr_unhandled_fault_notifier_chain, 0, NULL);
}

/* for reset,we use kernel native notifier chain */
static int register_dfx_reset_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "register reset notifier failed\n");
		return -EINVAL;
	}

	return register_reboot_notifier(nb);
}

static int unregister_dfx_reset_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "unregister reset notifier failed\n");
		return -EINVAL;
	}

	return unregister_reboot_notifier(nb);
}

/* for panic,we use panic_notifier_list from kernel */
static int register_dfx_panic_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "register early_panic notifier failed\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_register(&panic_notifier_list, nb);
}

static int unregister_dfx_panic_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "unregister early_panic notifier failed\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_unregister(&panic_notifier_list, nb);
}

#ifdef CONFIG_PPC
static ATOMIC_NOTIFIER_HEAD(dfx_mc_pre_notifier_chain);
static int register_dfx_mc_pre_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "register machine check  pre notifier failed\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_register(&dfx_mc_pre_notifier_chain, nb);
}

static int unregister_dfx_mc_pre_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "unregister machine check  pre notifier failed\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_unregister(&dfx_mc_pre_notifier_chain, nb);
}

int call_dfx_mc_pre_notifiers(void *v)
{
	return atomic_notifier_call_chain(&dfx_mc_pre_notifier_chain, 0, v);
}

static ATOMIC_NOTIFIER_HEAD(dfx_mc_post_notifier_chain);
static int register_dfx_mc_post_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "register machine check post notifier failed\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_register(&dfx_mc_post_notifier_chain, nb);
}

static int unregister_dfx_mc_post_notifier(struct notifier_block *nb)
{
	if (!nb) {
		printk(KERN_ERR "unregister machine check post notifier failed\n");
		return -EINVAL;
	}

	return atomic_notifier_chain_unregister(&dfx_mc_post_notifier_chain, nb);
}

int call_dfx_mc_post_notifiers(void *v)
{
	return atomic_notifier_call_chain(&dfx_mc_post_notifier_chain, 0, v);
}
#endif

/* we do not check user input @nb here,we check inside for more details */
int register_easy_dfx_notifier(enum EASY_DFX_NOTIFIER_TYPE type, struct notifier_block *nb)
{
	int ret = -EINVAL;
	if (type < EASY_DFX_USR_UNHANDLED_FAULT  || type >= EASY_DFX_MAX) {
		printk(KERN_ERR "unknown type:%u,please check!\n", type);
		return -EINVAL;
	}
	switch (type) {
	case EASY_DFX_USR_UNHANDLED_FAULT:
		ret = register_dfx_usr_unhandled_fault_notifier(nb);
		break;
	case EASY_DFX_KERNEL_DIE:
		ret = register_dfx_kernel_die_notifier(nb);
		break;
	case EASY_DFX_PANIC:
		ret = register_dfx_panic_notifier(nb);
		break;
	case EASY_DFX_RESET:
		ret = register_dfx_reset_notifier(nb);
		break;
#ifdef CONFIG_PPC
	case EASY_DFX_MC_PRE:
		ret = register_dfx_mc_pre_notifier(nb);
		break;
	case EASY_DFX_MC_POST:
		ret = register_dfx_mc_post_notifier(nb);
		break;
#endif
	default:
		break;
	}

	return ret;
}
EXPORT_SYMBOL_GPL(register_easy_dfx_notifier);

/* we do not check user input @nb here,we check inside for more details */
int unregister_easy_dfx_notifier(enum EASY_DFX_NOTIFIER_TYPE type, struct notifier_block *nb)
{
	int ret = -EINVAL;
	if (type < EASY_DFX_USR_UNHANDLED_FAULT || type >= EASY_DFX_MAX) {
		printk(KERN_ERR "unknown type:%u,please check!\n", type);
		return -EINVAL;
	}
	switch (type) {
	case EASY_DFX_USR_UNHANDLED_FAULT:
		ret = unregister_dfx_usr_unhandled_fault_notifier(nb);
		break;
	case EASY_DFX_KERNEL_DIE:
		ret = unregister_dfx_kernel_die_notifier(nb);
		break;
	case EASY_DFX_PANIC:
		ret = unregister_dfx_panic_notifier(nb);
		break;
	case EASY_DFX_RESET:
		ret = unregister_dfx_reset_notifier(nb);
		break;
#ifdef CONFIG_PPC
	case EASY_DFX_MC_PRE:
		ret = unregister_dfx_mc_pre_notifier(nb);
		break;
	case EASY_DFX_MC_POST:
		ret = unregister_dfx_mc_post_notifier(nb);
		break;
#endif
	default:
		break;
	}

	return ret;
}
EXPORT_SYMBOL_GPL(unregister_easy_dfx_notifier);

#if defined(CONFIG_ARM) || defined(CONFIG_ARM64)
EXPORT_TRACEPOINT_SYMBOL(ipi_entry);
#endif
